Добавил команду с выдачей погоды
This commit is contained in:
+72
-10
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
@@ -18,11 +20,13 @@ namespace ZaoBot
|
||||
static JsonDocument config;
|
||||
static HttpClient http = new HttpClient();
|
||||
|
||||
const string CMD_WEATHER = "Погода";
|
||||
const string CMD_LEFT_GATE = "Открой левые ворота";
|
||||
const string CMD_RIGHT_GATE = "Открой правые ворота";
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
|
||||
config = LoadConfig();
|
||||
var token = config.RootElement.GetProperty("token").GetString();
|
||||
botClient = new TelegramBotClient(token);
|
||||
@@ -48,18 +52,32 @@ namespace ZaoBot
|
||||
}
|
||||
Console.WriteLine($"Received a text message in chat {e.Message.Chat.Id}.");
|
||||
|
||||
var response = "";
|
||||
if (e.Message.Text == CMD_LEFT_GATE)
|
||||
{
|
||||
await OpenLeftGate();
|
||||
response = await OpenLeftGate();
|
||||
}
|
||||
if (e.Message.Text == CMD_RIGHT_GATE)
|
||||
else if (e.Message.Text == CMD_RIGHT_GATE)
|
||||
{
|
||||
await OpenRightGate();
|
||||
response = await OpenRightGate();
|
||||
}
|
||||
else if (e.Message.Text == CMD_WEATHER)
|
||||
{
|
||||
response = await ShowWeather();
|
||||
}
|
||||
else if (e.Message.Text == "")
|
||||
{
|
||||
response = "Превед!";
|
||||
}
|
||||
else response = "Я не понял :-(";
|
||||
|
||||
if (e.Message.Text != "") response += "\nЧто дальше?";
|
||||
else response = "\nЧего изволите?";
|
||||
|
||||
var replyKeyboardMarkup = new ReplyKeyboardMarkup(
|
||||
new KeyboardButton[][]
|
||||
{
|
||||
new KeyboardButton[] { CMD_WEATHER },
|
||||
new KeyboardButton[] { CMD_LEFT_GATE, CMD_RIGHT_GATE },
|
||||
},
|
||||
resizeKeyboard: true
|
||||
@@ -67,31 +85,75 @@ namespace ZaoBot
|
||||
|
||||
await botClient.SendTextMessageAsync(
|
||||
chatId: e.Message.Chat,
|
||||
text: "Чего изволите?",
|
||||
text: response,
|
||||
replyMarkup: replyKeyboardMarkup
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task OpenRightGate()
|
||||
private static async Task<string> ShowWeather()
|
||||
{
|
||||
try
|
||||
{
|
||||
string response = "";
|
||||
foreach (var element in config.RootElement.GetProperty("items_weather").EnumerateObject())
|
||||
{
|
||||
var url = config.RootElement.GetProperty("openhab_url").GetString();
|
||||
url += element.Name;
|
||||
var result = await http.GetAsync(url);
|
||||
var content = await result.Content.ReadAsStringAsync();
|
||||
response += element.Value + ": " + ParseContent(content) + "\n";
|
||||
}
|
||||
return response;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return "Fail: " + e.Message;
|
||||
}
|
||||
}
|
||||
|
||||
private static string ParseContent(string content)
|
||||
{
|
||||
var doc = JsonDocument.Parse(content);
|
||||
float value;
|
||||
if (!float.TryParse(doc.RootElement.GetProperty("state").GetString(), out value))
|
||||
{
|
||||
return "null";
|
||||
}
|
||||
try
|
||||
{
|
||||
var stateDescription = doc.RootElement.GetProperty("stateDescription");
|
||||
var pattern = stateDescription.GetProperty("pattern");
|
||||
return printf.Printf.sprintf(pattern.GetString(), value);
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<string> OpenRightGate()
|
||||
{
|
||||
var url = config.RootElement.GetProperty("openhab_url").GetString();
|
||||
url += config.RootElement.GetProperty("item_right_gate").GetString();
|
||||
await Post(url, "ON");
|
||||
return await Post(url, "ON");
|
||||
}
|
||||
|
||||
private static async Task Post(string url, string v)
|
||||
private static async Task<string> Post(string url, string v)
|
||||
{
|
||||
var content = new ByteArrayContent(Encoding.UTF8.GetBytes(v));
|
||||
content.Headers.Add("Content-Type", "text/plain");
|
||||
await http.PostAsync(url, content);
|
||||
var result = await http.PostAsync(url, content);
|
||||
if (result.IsSuccessStatusCode) return "OK";
|
||||
else return "Fail. " + result.ReasonPhrase;
|
||||
|
||||
}
|
||||
|
||||
private static async Task OpenLeftGate()
|
||||
private static async Task<string> OpenLeftGate()
|
||||
{
|
||||
var url = config.RootElement.GetProperty("openhab_url").GetString();
|
||||
url += config.RootElement.GetProperty("item_left_gate").GetString();
|
||||
await Post(url, "ON");
|
||||
return await Post(url, "ON");
|
||||
}
|
||||
|
||||
private static bool VerifyUser(long id, string username)
|
||||
|
||||
@@ -0,0 +1,535 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace printf {
|
||||
/// <summary>
|
||||
/// Applies string formatting to objects using a "printf" format string.
|
||||
/// Can be extended to use custom formats.
|
||||
/// </summary>
|
||||
public class FormatObject {
|
||||
///<summary>
|
||||
/// The default precision for floating point numbers
|
||||
/// </summary>
|
||||
public const int DefaultPrecision = 6;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a formatting part of the format string (i.e. %1.3d)
|
||||
/// All fields are interpreted as specified in the C language specification
|
||||
/// </summary>
|
||||
public class FormatStringPart {
|
||||
/// <summary>
|
||||
/// Minimum width
|
||||
/// </summary>
|
||||
public int width = 0;
|
||||
/// <summary>
|
||||
/// Precision
|
||||
/// </summary>
|
||||
public int? precision;
|
||||
/// <summary>
|
||||
/// Length (h or l)
|
||||
/// </summary>
|
||||
public char length;
|
||||
/// <summary>
|
||||
/// Specifier (s for strings, d for integers etc.)
|
||||
/// </summary>
|
||||
public char specifier;
|
||||
|
||||
/// <summary>
|
||||
/// Left aligns the result instead of the default right align
|
||||
/// </summary>
|
||||
public bool LeftAlign = false;
|
||||
/// <summary>
|
||||
/// Displays + cahracter for positive numbers (by default it is omitted)
|
||||
/// </summary>
|
||||
public bool ForcePlus = false;
|
||||
/// <summary>
|
||||
/// Displays ' ' (space) character for positive numbers (by default it is omitted)
|
||||
/// </summary>
|
||||
public bool BlankIfPlus = false;
|
||||
/// <summary>
|
||||
/// Has different meanings for format specifiers
|
||||
/// </summary>
|
||||
public bool HashMark = false;
|
||||
/// <summary>
|
||||
/// If LeftAlign = false, pads with the '0' character instead of ' '.
|
||||
/// If LeftAlign = true, this flag is ignored.
|
||||
/// </summary>
|
||||
public bool PadWithZero = false;
|
||||
}
|
||||
|
||||
//List of actual arguments
|
||||
List<object> args = new List<object>();
|
||||
|
||||
//Number of required arguments
|
||||
int argCount = 0;
|
||||
|
||||
//Format string parts
|
||||
FormatStringPart[] parts;
|
||||
string[] staticParts;
|
||||
|
||||
string finalString;
|
||||
|
||||
enum ParseMode {
|
||||
Static, Format
|
||||
}
|
||||
enum FormatStep {
|
||||
Flags, Width, PrecisionStart, Precision, Length, Specifier
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new formatter with the default formatters (used by the printf functions).
|
||||
/// </summary>
|
||||
/// <param name="format"></param>
|
||||
public FormatObject(string format): this(format, true) {}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new formatter with or without the default formatters (used by the printf functions).
|
||||
/// </summary>
|
||||
/// <param name="format">The format string</param>
|
||||
/// <param name="useDefaultFormatters">Load the default formatters or not</param>
|
||||
public FormatObject(string format, bool useDefaultFormatters) {
|
||||
|
||||
//State machine to parse the format string
|
||||
//Surprisingly, the regexp version is not really shorter:
|
||||
//Parsing the string is easier, but extracting the results is longer
|
||||
|
||||
List<FormatStringPart> parts = new List<FormatStringPart>();
|
||||
List<string> staticParts = new List<string>();
|
||||
StringBuilder staticPart = new StringBuilder();
|
||||
|
||||
//Static mode: not in a % block, Format mode: in a % block
|
||||
ParseMode mode = ParseMode.Static;
|
||||
FormatStringPart part = null;
|
||||
FormatStep step = FormatStep.Flags;
|
||||
StringBuilder tempBuffer = null;
|
||||
|
||||
for (int i = 0; i < format.Length; ++i) {
|
||||
char c = format[i];
|
||||
|
||||
if (mode == ParseMode.Static) {
|
||||
//If
|
||||
if (c == '%') {
|
||||
mode = ParseMode.Format;
|
||||
step = FormatStep.Flags;
|
||||
}
|
||||
else {
|
||||
staticPart.Append(c);
|
||||
}
|
||||
}
|
||||
else {
|
||||
//For the terminology (flags, specifiers etc.) look up any printf documentation.
|
||||
switch (step) {
|
||||
case FormatStep.Flags:
|
||||
if (c == '%') {
|
||||
staticPart.Append('%');
|
||||
mode = ParseMode.Static;
|
||||
continue;
|
||||
}
|
||||
if (part == null) part = new FormatStringPart();
|
||||
if (c == '-') {
|
||||
part.LeftAlign = true;
|
||||
}
|
||||
else if (c == '+') {
|
||||
part.ForcePlus = true;
|
||||
}
|
||||
else if (c == ' ') {
|
||||
part.BlankIfPlus = true;
|
||||
}
|
||||
else if (c == '#') {
|
||||
part.HashMark = true;
|
||||
}
|
||||
else if (c == '0') {
|
||||
part.PadWithZero = true;
|
||||
}
|
||||
else {
|
||||
step = FormatStep.Width;
|
||||
goto case FormatStep.Width;
|
||||
}
|
||||
break;
|
||||
case FormatStep.Width:
|
||||
if (tempBuffer != null) {
|
||||
if (c >= '0' && c <= '9') {
|
||||
tempBuffer.Append(c);
|
||||
}
|
||||
else {
|
||||
part.width = int.Parse(tempBuffer.ToString());
|
||||
tempBuffer = null;
|
||||
step = FormatStep.PrecisionStart;
|
||||
goto case FormatStep.PrecisionStart;
|
||||
}
|
||||
}
|
||||
else if (c == '*') {
|
||||
part.width = -1;
|
||||
argCount++;
|
||||
}
|
||||
else if (c >= '0' && c <= '9') {
|
||||
tempBuffer = new StringBuilder();
|
||||
tempBuffer.Append(c);
|
||||
}
|
||||
else {
|
||||
step = FormatStep.PrecisionStart;
|
||||
goto case FormatStep.PrecisionStart;
|
||||
}
|
||||
break;
|
||||
case FormatStep.PrecisionStart:
|
||||
if (c != '.') {
|
||||
step = FormatStep.Length;
|
||||
goto case FormatStep.Length;
|
||||
}
|
||||
else {
|
||||
step = FormatStep.Precision;
|
||||
}
|
||||
break;
|
||||
case FormatStep.Precision:
|
||||
if (tempBuffer != null) {
|
||||
if (c >= '0' && c <= '9') {
|
||||
tempBuffer.Append(c);
|
||||
}
|
||||
else {
|
||||
part.precision = int.Parse(tempBuffer.ToString());
|
||||
tempBuffer = null;
|
||||
step = FormatStep.Length;
|
||||
goto case FormatStep.Length;
|
||||
}
|
||||
}
|
||||
else if (c == '*') {
|
||||
part.precision = -1;
|
||||
argCount++;
|
||||
}
|
||||
else if (c >= '0' && c <= '9') {
|
||||
tempBuffer = new StringBuilder();
|
||||
tempBuffer.Append(c);
|
||||
}
|
||||
else {
|
||||
part.precision = 0;
|
||||
step = FormatStep.Length;
|
||||
goto case FormatStep.Length;
|
||||
}
|
||||
break;
|
||||
case FormatStep.Length:
|
||||
if ("hlL".IndexOf(c) != -1) {
|
||||
part.length = c;
|
||||
step = FormatStep.Specifier;
|
||||
}
|
||||
else {
|
||||
step = FormatStep.Specifier;
|
||||
goto case FormatStep.Specifier;
|
||||
}
|
||||
break;
|
||||
case FormatStep.Specifier:
|
||||
argCount++;
|
||||
part.specifier = c;
|
||||
|
||||
staticParts.Add(staticPart.ToString());
|
||||
staticPart = new StringBuilder();
|
||||
parts.Add(part);
|
||||
part = null;
|
||||
|
||||
mode = ParseMode.Static;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mode == ParseMode.Format) {
|
||||
throw new ArgumentException("Invalid format string", "format");
|
||||
}
|
||||
staticParts.Add(staticPart.ToString());
|
||||
|
||||
this.parts = parts.ToArray();
|
||||
this.staticParts = staticParts.ToArray();
|
||||
|
||||
if (useDefaultFormatters) LoadDefaultFormatters();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the objects to format.
|
||||
/// If they are already set, the function will overwrite them.
|
||||
/// This is useful if you want to use the same format string to
|
||||
/// format many objects.
|
||||
/// </summary>
|
||||
/// <param name="objs">The list of objects</param>
|
||||
public void SetArgs(params object[] objs) {
|
||||
for (int i = 0; i < objs.Length; ++i) {
|
||||
args.Add(objs[i]);
|
||||
}
|
||||
this.finalString = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats the object according to the FormatStringPart argument.
|
||||
/// If you create a custom formatter, you should ignore the width property,
|
||||
/// as padding is done independently for all format types.
|
||||
/// </summary>
|
||||
/// <param name="part">It contains the flags, specifier etc.</param>
|
||||
/// <param name="arg">The object to format</param>
|
||||
public delegate FormatResult Formatter(FormatStringPart part, object arg);
|
||||
|
||||
Dictionary<char, Formatter> formatters = new Dictionary<char, Formatter>();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a custom formatter.
|
||||
/// </summary>
|
||||
/// <param name="specifier">The character in the format string (e.g. 's' for %s)</param>
|
||||
/// <param name="formatter">The formatter function</param>
|
||||
public void AddFormatter(char specifier, Formatter formatter) {
|
||||
formatters.Add(specifier, formatter);
|
||||
}
|
||||
|
||||
private void DoFormat() {
|
||||
try {
|
||||
if (argCount > args.Count) {
|
||||
throw new InvalidOperationException(string.Format(
|
||||
"Expected argument count: {0}, provided: {1}", argCount, args.Count));
|
||||
}
|
||||
StringBuilder final = new StringBuilder();
|
||||
for (int i = 0, j = 0; i < parts.Length; ++i, ++j) {
|
||||
final.Append(staticParts[i]);
|
||||
var f = parts[j];
|
||||
if (f.width == -1) {
|
||||
f.width = (int)args[j];
|
||||
j++;
|
||||
}
|
||||
if (f.precision == -1) {
|
||||
f.width = (int)args[j];
|
||||
j++;
|
||||
}
|
||||
FormatResult afterSpecifiers = formatters[f.specifier].Invoke(f, args[j]);
|
||||
int pad = f.width - afterSpecifiers.Result.Length - afterSpecifiers.Sign.Length;
|
||||
if (pad > 0 && !f.LeftAlign) {
|
||||
if (f.PadWithZero) {
|
||||
final.Append(afterSpecifiers.Sign);
|
||||
final.Append(new string('0', pad));
|
||||
}
|
||||
else {
|
||||
final.Append(new string(' ', pad));
|
||||
final.Append(afterSpecifiers.Sign);
|
||||
}
|
||||
}
|
||||
else {
|
||||
final.Append(afterSpecifiers.Sign);
|
||||
}
|
||||
final.Append(afterSpecifiers.Result);
|
||||
if (pad > 0 && f.LeftAlign) {
|
||||
final.Append(new string(' ', pad));
|
||||
}
|
||||
}
|
||||
final.Append(staticParts[staticParts.Length-1]);
|
||||
finalString = final.ToString();
|
||||
}
|
||||
catch (KeyNotFoundException ex) {
|
||||
throw new InvalidOperationException("Invalid specifier in format string", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the formatted string.
|
||||
/// </summary>
|
||||
/// <returns>The formatted string</returns>
|
||||
public override string ToString() {
|
||||
if (this.finalString == null) DoFormat();
|
||||
return finalString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The results of the Formatter type functions.
|
||||
/// </summary>
|
||||
public struct FormatResult {
|
||||
/// <summary>
|
||||
/// The resulting strings sign, if any.
|
||||
/// </summary>
|
||||
public string Sign {get; set;}
|
||||
/// <summary>
|
||||
/// The resulting string, without the sign if any.
|
||||
/// </summary>
|
||||
public string Result {get; set;}
|
||||
|
||||
/// <summary>
|
||||
/// Converts to string into a FormatResult object.
|
||||
/// The string is assigned to the Result property of the created FormatString,
|
||||
/// the Sign property will be "" (empty string).
|
||||
/// </summary>
|
||||
/// <param name="s">The string to convert</param>
|
||||
/// <returns>A FormatResult object</returns>
|
||||
public static implicit operator FormatResult(string s) {
|
||||
return new FormatResult {
|
||||
Sign = "", Result = s
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private string IntPrecision(string str, FormatStringPart part) {
|
||||
if (str.Equals("0") && part.precision == 0) return "";
|
||||
else if (str.Length < part.precision) {
|
||||
return new string('0', (part.precision ?? 1) - str.Length) + str;
|
||||
}
|
||||
else {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadDefaultFormatters() {
|
||||
var format = new System.Globalization.NumberFormatInfo();
|
||||
format.NumberDecimalSeparator = ".";
|
||||
|
||||
Formatter charFormatter = (part, arg) => {
|
||||
char c = (char)arg;
|
||||
return c.ToString();
|
||||
};
|
||||
AddFormatter('c', charFormatter);
|
||||
Formatter intFormatter = (part, arg) => {
|
||||
long i = Convert.ToInt64(arg);
|
||||
string sign = "";
|
||||
if (i >= 0) {
|
||||
if (part.ForcePlus) sign = "+";
|
||||
else if (part.BlankIfPlus) sign = " ";
|
||||
}
|
||||
else {
|
||||
i = -i;
|
||||
sign = "-";
|
||||
}
|
||||
return new FormatResult { Result = IntPrecision(i.ToString(), part), Sign = sign };
|
||||
};
|
||||
AddFormatter('i', intFormatter);
|
||||
AddFormatter('d', intFormatter);
|
||||
Formatter uintFormatter = (part, arg) => {
|
||||
ulong u = Convert.ToUInt64(arg);
|
||||
string sign = "";
|
||||
if (u >= 0) {
|
||||
if (part.ForcePlus) sign = "+";
|
||||
else if (part.BlankIfPlus) sign = " ";
|
||||
}
|
||||
return new FormatResult { Result = IntPrecision(u.ToString(), part) , Sign = sign };
|
||||
};
|
||||
AddFormatter('u', uintFormatter);
|
||||
Formatter sciFormatter = (part, arg) => {
|
||||
double d = Convert.ToDouble(arg);
|
||||
string sign = "";
|
||||
if (d >= 0 || double.IsNaN(d)) {
|
||||
if (part.ForcePlus) sign = "+";
|
||||
else if (part.BlankIfPlus) sign = " ";
|
||||
}
|
||||
else {
|
||||
d = -d;
|
||||
sign = "-";
|
||||
}
|
||||
string retStr = d.ToString(string.Concat("0.",
|
||||
new string('0', part.precision ?? DefaultPrecision),
|
||||
part.specifier.ToString(),
|
||||
"+000"),
|
||||
format);
|
||||
//# flag: place decimal point even if not needed
|
||||
if (part.HashMark && !retStr.Contains(format.NumberDecimalSeparator)) {
|
||||
retStr = string.Join(format.NumberDecimalSeparator + part.specifier,
|
||||
retStr.Split(part.specifier));
|
||||
}
|
||||
return new FormatResult {
|
||||
Result = retStr,
|
||||
Sign = sign
|
||||
};
|
||||
};
|
||||
AddFormatter('e', sciFormatter);
|
||||
AddFormatter('E', sciFormatter);
|
||||
Formatter floatFormatter = (part, arg) => {
|
||||
double d = Convert.ToDouble(arg);
|
||||
string sign = "";
|
||||
if (d >= 0) {
|
||||
if (part.ForcePlus) sign = "+";
|
||||
else if (part.BlankIfPlus) sign = " ";
|
||||
}
|
||||
else {
|
||||
d = -d;
|
||||
sign = "-";
|
||||
}
|
||||
format.NumberDecimalDigits = part.precision ?? DefaultPrecision;
|
||||
string retStr = d.ToString("f", format);
|
||||
//# flag: place decimal point even if not needed
|
||||
if (part.HashMark && !retStr.Contains(format.NumberDecimalSeparator)) {
|
||||
retStr += format.NumberDecimalSeparator;
|
||||
}
|
||||
return new FormatResult { Result = retStr, Sign = sign };
|
||||
};
|
||||
AddFormatter('f', floatFormatter);
|
||||
|
||||
Formatter gFormatter = (part, arg) => {
|
||||
double d = Convert.ToDouble(arg);
|
||||
string sign = "";
|
||||
if (d >= 0) {
|
||||
if (part.ForcePlus) sign = "+";
|
||||
else if (part.BlankIfPlus) sign = " ";
|
||||
}
|
||||
else {
|
||||
d = -d;
|
||||
sign = "-";
|
||||
}
|
||||
format.NumberDecimalDigits = part.precision ?? DefaultPrecision;
|
||||
/*string formatString = part.specifier.ToString() + part.precision.ToString() +
|
||||
(part.HashMark ? ":0." + new string('#', part.precision ?? DefaultPrecision) : "");
|
||||
string retStr = string.Format(format, formatString, d);*/
|
||||
//Could not make it work
|
||||
string retStr = d.ToString(part.specifier.ToString() + part.precision.ToString(), format);
|
||||
return new FormatResult { Result = retStr, Sign = sign };
|
||||
};
|
||||
AddFormatter('g', gFormatter);
|
||||
AddFormatter('G', gFormatter);
|
||||
|
||||
Formatter octFormatter = (part, arg) => {
|
||||
long l = Convert.ToInt64(arg);
|
||||
string retStr = IntPrecision(Convert.ToString(l, 8), part);
|
||||
//# flag: put a 0 before tha number
|
||||
if (part.HashMark) {
|
||||
return "0" + retStr;
|
||||
}
|
||||
else {
|
||||
return retStr;
|
||||
}
|
||||
};
|
||||
AddFormatter('o', octFormatter);
|
||||
Formatter hexFormatter = (part, arg) => {
|
||||
string retStr;
|
||||
if (part.length == 'h') {
|
||||
short s = Convert.ToInt16(arg);
|
||||
retStr = string.Format(
|
||||
part.specifier == 'x' ? "{0:x}" : "{0:X}",
|
||||
s);
|
||||
}
|
||||
else if (part.length == 'l') {
|
||||
long l = Convert.ToInt64(arg);
|
||||
retStr = string.Format(
|
||||
part.specifier == 'x' ? "{0:x}" : "{0:X}",
|
||||
l);
|
||||
}
|
||||
else {
|
||||
int i = Convert.ToInt32(arg);
|
||||
retStr = string.Format(
|
||||
part.specifier == 'x' ? "{0:x}" : "{0:X}",
|
||||
i);
|
||||
}
|
||||
//# flag: put a 0x before tha number
|
||||
return new FormatResult {
|
||||
Result = IntPrecision(retStr, part),
|
||||
Sign = part.HashMark ? "0" + part.specifier : ""
|
||||
};
|
||||
};
|
||||
AddFormatter('x', hexFormatter);
|
||||
AddFormatter('X', hexFormatter);
|
||||
Formatter strFormatter = (part, arg) => {
|
||||
if (part.precision != null) {
|
||||
return arg.ToString().Remove(part.precision.Value);
|
||||
}
|
||||
else {
|
||||
return arg.ToString();
|
||||
}
|
||||
};
|
||||
AddFormatter('s', strFormatter);
|
||||
Formatter ptrFormatter = (part, arg) => {
|
||||
return arg.GetHashCode().ToString();
|
||||
};
|
||||
AddFormatter('p', ptrFormatter);
|
||||
AddFormatter('n', (part, arg) => {
|
||||
throw new NotSupportedException("Getting the number of characters is not supported");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
|
||||
#if TEST
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace printf {
|
||||
[TestFixture]
|
||||
public class FormatTest {
|
||||
string decSep = System.Globalization.NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator;
|
||||
|
||||
[Test]
|
||||
public void TestTypes() {
|
||||
//Test the basic data types
|
||||
Assert.AreEqual("b",
|
||||
Printf.sprintf("%c", 'b'));
|
||||
Assert.AreEqual("121",
|
||||
Printf.sprintf("%d", 121));
|
||||
Assert.AreEqual("-34",
|
||||
Printf.sprintf("%i", -34));
|
||||
Assert.AreEqual("3.123400e+002",
|
||||
Printf.sprintf("%e", 3.1234e02));
|
||||
Assert.AreEqual("3.123400E+002",
|
||||
Printf.sprintf("%E", 3.1234e02));
|
||||
Assert.AreEqual("3.123000",
|
||||
Printf.sprintf("%f", 3.123));
|
||||
Assert.AreEqual("610",
|
||||
Printf.sprintf("%o", Convert.ToInt32("0610", 8)));
|
||||
Assert.AreEqual("asdfjkl",
|
||||
Printf.sprintf("%s", "asdfjkl"));
|
||||
Assert.AreEqual("4567",
|
||||
Printf.sprintf("%u", 4567));
|
||||
Assert.AreEqual("deadf00d",
|
||||
Printf.sprintf("%x", unchecked((int)0xdeadf00d)));
|
||||
Assert.AreEqual("DEADF00D",
|
||||
Printf.sprintf("%X", unchecked((int)0xdeadf00d)));
|
||||
Assert.IsNotEmpty(Printf.sprintf("%p", new object()));
|
||||
|
||||
Assert.Throws<NotSupportedException>(delegate() {
|
||||
Printf.sprintf("%n", new IntPtr());
|
||||
});
|
||||
|
||||
//Invalid format string
|
||||
Assert.Throws<ArgumentException>(delegate() {
|
||||
Printf.sprintf("%z", new IntPtr());
|
||||
});
|
||||
|
||||
Assert.AreEqual("%",
|
||||
Printf.sprintf("%%"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStaticText() {
|
||||
//Static text
|
||||
Assert.AreEqual("aaaa",
|
||||
Printf.sprintf("aaaa"));
|
||||
//Param at the end
|
||||
Assert.AreEqual("Hello World",
|
||||
Printf.sprintf("Hello %s", "World"));
|
||||
//Param at the beginning
|
||||
Assert.AreEqual("Hello World!",
|
||||
Printf.sprintf("%s World!", "Hello"));
|
||||
//Param in the middle
|
||||
Assert.AreEqual("a 1 b",
|
||||
Printf.sprintf("a %d b", 1));
|
||||
//Multiple params
|
||||
Assert.AreEqual("Hello World!",
|
||||
Printf.sprintf("%s %s!", "Hello", "World"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestArgumentCount() {
|
||||
//More arguments should not cause errors
|
||||
Assert.DoesNotThrow(delegate() {
|
||||
Printf.sprintf("%d %d", 1, 2, 3);
|
||||
});
|
||||
//Less arguments should raise error
|
||||
Assert.Throws<ArgumentException>(delegate() {
|
||||
Printf.sprintf("%d %d", 1);
|
||||
});
|
||||
//Also check for the extra arguments required for *
|
||||
Assert.Throws<ArgumentException>(delegate() {
|
||||
Printf.sprintf("%*d", 1);
|
||||
});
|
||||
Assert.Throws<ArgumentException>(delegate() {
|
||||
Printf.sprintf("%.*d", 1);
|
||||
});
|
||||
Assert.Throws<ArgumentException>(delegate() {
|
||||
Printf.sprintf("%*.*d", 1, 2);
|
||||
});
|
||||
Assert.DoesNotThrow(delegate() {
|
||||
Printf.sprintf("%*.*d", 1, 2, 3);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPadding() {
|
||||
//Simple padding
|
||||
Assert.AreEqual(" 33",
|
||||
Printf.sprintf("%5d", 33));
|
||||
//More padding than required
|
||||
Assert.AreEqual("123456",
|
||||
Printf.sprintf("%4d", 123456));
|
||||
//Left-align
|
||||
Assert.AreEqual("888 ",
|
||||
Printf.sprintf("%-5d", 888));
|
||||
//Pad with zeros
|
||||
Assert.AreEqual("0043, 00zz",
|
||||
Printf.sprintf("%04d, %04s", 43, "zz"));
|
||||
//Left align always pads with spaces
|
||||
Assert.AreEqual("123 ",
|
||||
Printf.sprintf("%-05d", 123));
|
||||
//Width specified on the argument list
|
||||
Assert.AreEqual(" 11",
|
||||
Printf.sprintf("%*d", 6, 11));
|
||||
//Sign with padding
|
||||
Assert.AreEqual(" -1 -0001 0001",
|
||||
Printf.sprintf("%5d %05d % 05d", -1, -1, 1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSign() {
|
||||
//Sign of number
|
||||
Assert.AreEqual("-5",
|
||||
Printf.sprintf("%d", -5));
|
||||
//Positive signs
|
||||
Assert.AreEqual("3 +4",
|
||||
Printf.sprintf("%d %+d", 3, 4));
|
||||
//Sign is not printed for o x and X
|
||||
Assert.AreEqual("610 ffffffff FFFFFFFF",
|
||||
Printf.sprintf("%+o %+x %+X", Convert.ToInt32("0610", 8), -1, -1));
|
||||
//Insert space if there is no sign
|
||||
Assert.AreEqual("-11 22",
|
||||
Printf.sprintf("% d % d", -11, 22));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPrecision() {
|
||||
//INTEGERS: minimum number of digits
|
||||
Assert.AreEqual("123",
|
||||
Printf.sprintf("%.2d", 123));
|
||||
Assert.AreEqual("ff",
|
||||
Printf.sprintf("%.1x", 0xff));
|
||||
//Padding with 0
|
||||
Assert.AreEqual("0044",
|
||||
Printf.sprintf("%.4d", 44));
|
||||
Assert.AreEqual("000FF",
|
||||
Printf.sprintf("%.5X", 0xff));
|
||||
Assert.AreEqual("003",
|
||||
Printf.sprintf("%.3u", 3));
|
||||
Assert.AreEqual("0010",
|
||||
Printf.sprintf("%.4o", 8));
|
||||
//Sign is not included:
|
||||
Assert.AreEqual("-01",
|
||||
Printf.sprintf("%.2d", -1));
|
||||
//Mixing:
|
||||
Assert.AreEqual(" 001",
|
||||
Printf.sprintf("%5.3d", 1));
|
||||
|
||||
//Zero:
|
||||
Assert.AreEqual("00",
|
||||
Printf.sprintf("%.2d", 0));
|
||||
Assert.AreEqual("",
|
||||
Printf.sprintf("%.0d", 0));
|
||||
Assert.AreEqual("",
|
||||
Printf.sprintf("%.d", 0));
|
||||
|
||||
//Floating point:
|
||||
Assert.AreEqual("0.1235",
|
||||
Printf.sprintf("%.4f", 0.123456));
|
||||
Assert.AreEqual("0",
|
||||
Printf.sprintf("%.0f", 0.123456));
|
||||
//Mixing:
|
||||
Assert.AreEqual(" 0.123",
|
||||
Printf.sprintf("%6.3f", 0.123));
|
||||
Assert.AreEqual("0.123",
|
||||
Printf.sprintf("%4.3f", 0.123));
|
||||
|
||||
//Scientific:
|
||||
Assert.AreEqual("1.2346e-001",
|
||||
Printf.sprintf("%.4e", 0.123456));
|
||||
Assert.AreEqual("1e-001",
|
||||
Printf.sprintf("%.0e", 0.123456));
|
||||
//Mixing:
|
||||
Assert.AreEqual("1.235e-001",
|
||||
Printf.sprintf("%4.3e", 0.123456));
|
||||
Assert.AreEqual(" 1.235e-001",
|
||||
Printf.sprintf("%15.3e", 0.123456));
|
||||
|
||||
|
||||
//String:
|
||||
Assert.AreEqual("asd",
|
||||
Printf.sprintf("%.3s", "asdfjkl"));
|
||||
Assert.AreEqual("",
|
||||
Printf.sprintf("%.s", "asdfjkl"));
|
||||
//Mixing:
|
||||
Assert.AreEqual(" asd",
|
||||
Printf.sprintf("%6.3s", "asdfjkl"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHashMark() {
|
||||
//Prefix for o, h and H
|
||||
Assert.AreEqual("011 0xff 0XABC",
|
||||
Printf.sprintf("%#o %#x %#X", 9, 0xff, 0xabc));
|
||||
|
||||
//Decimal point for f, e and E
|
||||
Assert.AreEqual("1. 2.e+000 3.E+000",
|
||||
Printf.sprintf("%#.0f %#.0e %#.0E", 1.1, 2.1, 3.1));
|
||||
Assert.AreEqual("1.1 2.1e+000 3.1E+000",
|
||||
Printf.sprintf("%#.1f %#.1e %#.1E", 1.1, 2.1, 3.1));
|
||||
|
||||
//Mixing with 0 and padding
|
||||
Assert.AreEqual("0x0ab",
|
||||
Printf.sprintf("%#05x", 0xab));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGSpecifier() {
|
||||
Assert.AreEqual("0.0001",
|
||||
Printf.sprintf("%g", 0.0001));
|
||||
Assert.AreEqual("1e-05",
|
||||
Printf.sprintf("%g", 0.00001));
|
||||
Assert.AreEqual("1E-05",
|
||||
Printf.sprintf("%G", 0.00001));
|
||||
|
||||
//Trailing zeros
|
||||
Assert.AreEqual("0.01",
|
||||
Printf.sprintf("%.3g", 0.01));
|
||||
Assert.AreEqual("0.010",
|
||||
Printf.sprintf("%.3f", 0.01));
|
||||
//Assert.AreEqual("0.010",
|
||||
// Printf.sprintf("%#.3g", 0.01));
|
||||
|
||||
//Precision = significant digits
|
||||
Assert.AreEqual(" 10.1",
|
||||
Printf.sprintf("%9.3g", 10.111111));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,69 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace printf {
|
||||
/// <summary>
|
||||
/// Contains the printf family of functions.
|
||||
/// It supports the following formats: 'diufeExXoscp';
|
||||
/// The following flags: '-+ #0';
|
||||
/// Width and precision;
|
||||
/// Length (h, l) for hexadecimal output (for other formats, integers are treated as Int64 or UInt64)
|
||||
///
|
||||
/// Decimal separator is not localized, it is always a '.' character.
|
||||
/// %g and %G does not remove trailing zeroes.
|
||||
/// NaN and infinities are not guaranteed to have a fixed representation accross platforms.
|
||||
///
|
||||
/// At the moment, it does NOT support %n (number of chars printed, through a prointer),
|
||||
/// and the 1$..n$ position specifier.
|
||||
/// </summary>
|
||||
public static class Printf {
|
||||
/// <summary>
|
||||
/// Formats the arguments according to the format string.
|
||||
/// Returns the result as a string.
|
||||
/// </summary>
|
||||
/// <param name="format">The format string</param>
|
||||
/// <param name="args">The objects to format</param>
|
||||
/// <returns>The formatted output</returns>
|
||||
/// <exception cref="ArgumentException">The format string is invalid or too few arguments provided.</exception>
|
||||
/// <exception cref="ArgumentNullException">Format string is null</exception>
|
||||
public static string sprintf(string format, params object[] args) {
|
||||
if (format == null) throw new ArgumentNullException("format");
|
||||
try {
|
||||
FormatObject f = new FormatObject(format);
|
||||
f.SetArgs(args);
|
||||
return f.ToString();
|
||||
}
|
||||
catch(InvalidOperationException ex) {
|
||||
throw new ArgumentException("Error in format string or arguments, see inner exception for details", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats the arguments according to the format string,
|
||||
/// and writes the formatted string to the standard output.
|
||||
/// </summary>
|
||||
/// <param name="format">The format string</param>
|
||||
/// <param name="args">The objects to format</param>
|
||||
/// <exception cref="ArgumentException">The format string is invalid or too few arguments provided.</exception>
|
||||
/// <exception cref="ArgumentNullException">Format string is null</exception>
|
||||
public static void printf(string format, params object[] args) {
|
||||
if (format == null) throw new ArgumentNullException("format");
|
||||
Console.Write(sprintf(format, args));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats the arguments according to the format string,
|
||||
/// and writes the formatted string to the specified TextWriter.
|
||||
/// </summary>
|
||||
/// <param name="writer">The target TextWriter</param>
|
||||
/// <param name="format">The format string</param>
|
||||
/// <param name="args">The objects to format</param>
|
||||
/// <exception cref="ArgumentException">The format string is invalid or too few arguments provided.</exception>
|
||||
/// <exception cref="ArgumentNullException">Format string or TextWriter is null</exception>
|
||||
public static void fprintf(System.IO.TextWriter writer, string format, params object[] args) {
|
||||
if (format == null) throw new ArgumentNullException("format");
|
||||
if (writer == null) throw new ArgumentNullException("writer");
|
||||
writer.Write(sprintf(format, args));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user